package com.yunxi.order.service.impl;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.yunxi.order.bean.Order;
import com.yunxi.order.feign.ProductFeignClient;
import com.yunxi.order.service.OrderService;
import com.yunxi.product.bean.Product;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.math.BigDecimal;
import java.util.List;

/**
 * 订单实现
 *
 * @author: lidy
 * @date: 2025-01-17 11:24
 */
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    private DiscoveryClient discoveryClient;

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private LoadBalancerClient loadBalancerClient;

    @Resource
    private ProductFeignClient productFeignClient;

    /**
     * 创建订单
     *
     * @param productId 商品编号
     * @param userId    用户编号
     * @return 订单信息
     */
    @SentinelResource(value = "createOrder", blockHandler = "createOrderHandler")   // 标记该方法需要被 Sentinel 监控
    @Override
    public Order createOrder(Long productId, Long userId) {
        // 远程获取商品信息

        // 使用 RestTemplate 发送请求
        // 没有负载均衡的
//        Product product = getProductFromRemote(productId);
        // 使用 LoadBalance 负载均衡
//        Product product = getProductFromRemoteWithLoadBalance(productId);
        // 使用 注解式 负载均衡
//        Product product = getProductFromRemoteWithLoadBalanceAnnotation(productId);


        // 使用 OpenFeign 发送请求
        Product product = productFeignClient.getProductById(productId);

        Order order = new Order();

        order.setId(1L);

        // 根据商品计算总价
        order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
        order.setUserId(userId);
        order.setNickname("李子夜");
        order.setAddress("广东省广州市天河区");

        // 远程调用商品数据
        order.setProductList(List.of(product));

        return order;
    }

    /**
     * 处理 @SentinelResource 注解标注的资源异常
     * 1. 在注解上使用 blockHandler 指定兜底异常处理器
     * 2. 编写异常处理方法，一般和标注的方法一致，只是参数多了个 BlockException
     */
    public Order createOrderHandler(Long productId, Long userId, BlockException e) {
        Order order = new Order();
        order.setId(0L);
        order.setTotalAmount(new BigDecimal("0"));
        order.setUserId(userId);
        order.setNickname("未知用户");
        order.setAddress("错误原因：" + e.getClass());

        return order;
    }

    /**
     * 远程调用商品服务，获取商品数据
     * 版本1：没有负载均衡，手动选择调用哪一个
     *
     * @param productId 商品id
     * @return 商品信息
     */
    private Product getProductFromRemote(Long productId) {
        // 1. 获得商品服务在注册中心的 IP+port
        List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
        // 取其中一个
        ServiceInstance instance = instances.get(0);
        // 拼接请求地址
        String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/product/" + productId;
        log.info("远程调用商品服务地址：{}", url);

        // 发送远程请求
        Product product = restTemplate.getForObject(url, Product.class);
        return product;
    }

    /**
     * 远程调用商品服务，获取商品数据
     * 版本2：使用 LoadBalance 负载均衡
     *
     * @param productId 商品id
     * @return 商品信息
     */
    private Product getProductFromRemoteWithLoadBalance(Long productId) {
        // 1. 获得商品服务在注册中心的 IP+port
        ServiceInstance choose = loadBalancerClient.choose("service-product");
        // 拼接请求地址
        String url = "http://" + choose.getHost() + ":" + choose.getPort() + "/product/" + productId;
        log.info("远程调用商品服务地址：{}", url);

        // 发送远程请求
        Product product = restTemplate.getForObject(url, Product.class);
        return product;
    }

    /**
     * 远程调用商品服务，获取商品数据
     * 版本3：使用 注解式 负载均衡
     * 实现：在 RestTemplate 的 bean 上添加  @LoadBalanced 即可
     *
     * @param productId 商品id
     * @return 商品信息
     */
    private Product getProductFromRemoteWithLoadBalanceAnnotation(Long productId) {
        // 将 IP+port 替换成服务名，restTemplate 在发送请求时，会负载均衡的选择一个可用的服务
        String url = "http://service-product/product/" + productId;
        // 发送远程请求
        Product product = restTemplate.getForObject(url, Product.class);
        return product;
    }

}
